﻿
//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

#include "CmdSnapWithInteractivePicker.h"
#include "IPick.h"
#include "ILine3d.h"
#include "Vector3d.h"
#include "IDocument.h"
#include "ActionBase.h"
#include "ISelection.h"
#include "IModelLine.h"
#include "IPickFilter.h"
#include "IPickTarget.h"
#include "IPickResult.h"
#include "IUiDocument.h"
#include "IHighlights.h"
#include "UiSnapUtils.h"
#include "ISnapContext.h"
#include "ISnapSettings.h"
#include "Vector3dUtils.h"
#include "IPreHighlights.h"
#include "IPickEventArgs.h"
#include "IGraphicsPlane.h"
#include "IPickCandidates.h"
#include "GcmpCommandState.h"
#include "IUserTransaction.h"
#include "IGraphicsCurve3d.h"
#include "IPickEventHandler.h"
#include "InteractivePicker.h"
#include "IElementModelShape.h"
#include "UiDocumentViewUtils.h"
#include "IPureGraphicsElement.h"
#include "IGraphicsElementShape.h"
#include "IGraphicsNodeReference.h"
#include "PickNodeReferenceOption.h"
#include "IPickNodeReferenceAction.h"

#include <iostream>
#include <Windows.h>
#include "SnapFilterDlg.h"
#include "CommandRegister.h"
#include "SampleUIUtils.h"
#include "PickSnapCommandIds.h"
#include "CmdSnapModuleEntry.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h" 

using namespace gcmp;
using namespace Sample;

#pragma region 捕捉过滤器和回调函数
namespace Sample
{
    // 捕捉的过滤器
    class SnapWithInteractivePickerActionFilter : public IPickFilter
    {
    public:
        SnapWithInteractivePickerActionFilter()
        {
        }

        virtual bool AllowElement(const gcmp::ElementId& elementId) const override
        {
            return true;
        }

        virtual bool AllowGraphicsNode(const gcmp::IGraphicsNodeReference& graphicsNodeReference) const override
        {
            return true;
        }

        virtual bool SetPickTargetOption(gcmp::IPickTarget* pPickTarget) override
        {
            pPickTarget->EnableAll();
            return true;
        }
    };

    // InteractivePicker::GetPoint在鼠标移动中的回调函数
    class SamplePickCallBack : public IPickCallback
    {

    public:
        SamplePickCallBack() : m_pFirstPoint(nullptr), m_tempElementId(ElementId::InvalidID)
        {
        }

        ~SamplePickCallBack()
        {
            // 删除临时图元
            if (m_tempElementId.IsValid())
            {
                IDocument* pDocument = UiDocumentViewUtils::GetCurrentDocument();
                pDocument->DeleteElement(m_tempElementId);
            }
        }

        // InteractivePicker::GetPoint鼠标移动中，绘制临时线
        virtual void OnMouseMove(IUiView* pCurrentView, const Vector3d &pickedPt, const Vector3d& mousePt) override
        {
            if (!m_pFirstPoint)
            {
                return;
            }

            IUiDocument* pUiDocument = pCurrentView->GetUiDocument();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pUiDocument, L"pUiDocument为空",L"GDMP",L"2023-12-30");
            IDocument* pDocument = pUiDocument->GetDbDocument();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pDocument, L"pDocument为空",L"GDMP",L"2023-12-30");

            IPureGraphicsElement* pPureGraphicsElement;

            if (!m_tempElementId.IsValid())
            {
                pPureGraphicsElement = IPureGraphicsElement::Create(pDocument, ElementCreationOptions::Transient);
                DBG_WARN_AND_RETURN_VOID_UNLESS(pPureGraphicsElement, L"pPureGraphicsElement为空",L"GDMP",L"2023-12-30");
                m_tempElementId = pPureGraphicsElement->GetElementId();
            }
            else
            {
                pPureGraphicsElement = quick_cast<IPureGraphicsElement>(pDocument->GetElement(m_tempElementId));
                DBG_WARN_AND_RETURN_VOID_UNLESS(pPureGraphicsElement, L"pPureGraphicsElement为空",L"GDMP",L"2023-12-30");
            }

            OwnerPtr<IGraphicsElementShape> opTempGRep = IGraphicsElementShape::Create(GraphicsRenderLayer::TransientObject);
            DBG_WARN_AND_RETURN_VOID_UNLESS(opTempGRep, L"opTempGRep为空",L"GDMP",L"2023-12-30");
            OwnerPtr<ILine3d> opLine = ILine3d::Create(*m_pFirstPoint, pickedPt);
            DBG_WARN_AND_RETURN_VOID_UNLESS(opLine, L"opLine为空",L"GDMP",L"2023-12-30");
            opTempGRep->AddChild(TransferOwnership(IGraphicsCurve3d::Create(opLine.get())));

            IElementModelShape* pGraphicsElementShapeBehavior = pPureGraphicsElement->GetElementModelShape();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pGraphicsElementShapeBehavior, L"pGraphicsElementShapeBehavior为空",L"GDMP",L"2023-12-30");
            pGraphicsElementShapeBehavior->SetGraphicsElementShape(TransferOwnership(opTempGRep));
        }

        void SetFirstPoint(const Vector3d& firstPoint)
        {
            m_pFirstPoint = &firstPoint;
        }
    private:
        const Vector3d* m_pFirstPoint;
        ElementId m_tempElementId;
    };
}
#pragma endregion 捕捉过滤器和回调函数

#pragma region CmdSnapWithInteractivePicker
REGISTER_COMMAND(CmdSnapWithInteractivePicker)

CmdSnapWithInteractivePicker::CmdSnapWithInteractivePicker() : CommandBase(ID_CMD_SNAP_WITH_INTERACTIVE_PICKER)
{
}

CmdSnapWithInteractivePicker::~CmdSnapWithInteractivePicker()
{
    // 在退出命令时，恢复全局ISnapSettings
    if (m_opGlobalSnapSettingsBackup)
    {
        ISnapSettings* pSnapSettings = ISnapSettings::GetGlobalSnapSettings();
        DBG_WARN_AND_RETURN_VOID_UNLESS(pSnapSettings, L"pSnapSettings为空",L"GDMP",L"2023-12-30");
        pSnapSettings->SetSnapSettings(m_opGlobalSnapSettingsBackup.get());
    }
}

bool CmdSnapWithInteractivePicker::IsEnabled() const
{
    return GcmpCommandState::IsInDocumentEnvironment() && (CmdSnapModuleEntry::GetSnapMethod() == SnapMethod::InteractivePicker);
}

bool CmdSnapWithInteractivePicker::IsVisible() const
{
    return GcmpCommandState::IsInDocumentEnvironment();
}

gcmp::OwnerPtr<gcmp::IAction> CmdSnapWithInteractivePicker::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    // 创建opSnapContext，设置近程捕捉和远程捕捉的过滤器
    OwnerPtr<ISnapContext> opSnapContext = ISnapContext::Create();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opSnapContext, L"opSnapContext为空",L"GDMP",L"2023-12-30");
    OwnerPtr<SnapWithInteractivePickerActionFilter> opLocalPickFilter = NEW_AS_OWNER_PTR(SnapWithInteractivePickerActionFilter);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opLocalPickFilter, L"opLocalPickFilter为空",L"GDMP",L"2023-12-30");
    OwnerPtr<SnapWithInteractivePickerActionFilter> opRemotePickFilter = NEW_AS_OWNER_PTR(SnapWithInteractivePickerActionFilter);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opRemotePickFilter, L"opRemotePickFilter为空",L"GDMP",L"2023-12-30");
    opSnapContext->SetFilterForLocalSnap(opLocalPickFilter.get());
    opSnapContext->SetFilterForRemoteSnap(opRemotePickFilter.get());

    // 捕捉的平面在教程中有介绍
    InteractivePicker::SnapPlaneType snapPlaneType = CmdSnapModuleEntry::GetPickerSnapPlaneType();
    // 在画布中显示坐标输入时，m_pickPointType为TwoDimensionPoint，会显示XY输入，ThreeDimensionPoint会显示XYZ输入
    InteractivePicker::PickPointType snapPointType = CmdSnapModuleEntry::GetPickerPickPointType();

    // UiSnapUtils::InitSnapContextFromUiView需要下面的设置，InteractivePicker::PickPointType和PickPointExchangeData::SnapPlaneType通用
    PickPointExchangeData::SnapPlaneType snapPlaneType_ExchangeData = (PickPointExchangeData::SnapPlaneType)snapPointType;

    // InteractivePicker::GetPoint用的是IPickPointAction，ISnapContext中的ISnapSettings不起作用，用的是全局ISnapSettings
    ISnapSettings* pSnapSettings = ISnapSettings::GetGlobalSnapSettings();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pSnapSettings, L"pSnapSettings为空",L"GDMP",L"2023-12-30");
    // 备份和缓存全局ISnapSettings，在退出时用于恢复全局ISnapSettings
    m_opGlobalSnapSettingsBackup = pSnapSettings->Clone();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(m_opGlobalSnapSettingsBackup, L"m_opGlobalSnapSettingsBackup为空",L"GDMP",L"2023-12-30");

    SnapFilterDlg* snapDlg = SnapFilterDlg::Get();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(snapDlg, L"snapDlg为空",L"GDMP",L"2023-12-30");

    // 循环拾取点并绘制模型线
    std::vector<Vector3d> snapPoints;
    Vector3d lastPoint;
    while (true)
    {
        bool isFirst = snapPoints.size() == 0;
        Vector3d* pLastPoint = isFirst ? nullptr : &lastPoint;

        // 下面的捕捉设置在while循环中，因此在每次开始捕捉点时都会用界面上的新设置
        OwnerPtr<SamplePickCallBack> opSamplePickCallBack = NEW_AS_OWNER_PTR(SamplePickCallBack);
        DBG_WARN_AND_BREAK_UNLESS(opSamplePickCallBack, L"opSamplePickCallBack为空",L"GDMP",L"2023-12-30");

        // 如果SnapPlaneType使用FirstPointPlane方式，这一步必须在ISnapContext::SetFirstPoint调用之后。因为FirstPoint会作为SnapPlane的原点
        if (!isFirst)
        {
            opSnapContext->SetFirstPoint(&lastPoint);
            opSamplePickCallBack->SetFirstPoint(lastPoint);
        }

        /// 初始化三维正交捕捉时的坐标系
        // UiSnapUtils用当前IUiView对ISnapContext做初始化
        IUiView* pUiView = UiDocumentViewUtils::GetCurrentUiView();
        DBG_WARN_AND_BREAK_UNLESS(pUiView, L"pUiView为空",L"GDMP",L"2023-12-30");
        bool doesInitSuccess = UiSnapUtils::InitSnapContextFromUiView(opSnapContext.get(), pUiView, (PickPointExchangeData::SnapPlaneType)snapPlaneType);
        DBG_WARN_AND_BREAK_UNLESS(doesInitSuccess, L"UiSnapUtils设置ISnapContext失败",L"GDMP",L"2023-12-30");

        // 可以用于测试SnapPlane和当前视图工作平面不一样时，PickPointExchangeData::m_planeType三种选项的区别
        if (CmdSnapModuleEntry::DoSetXYPlaneAsSnapPlane())
        {
            OwnerPtr<IGraphicsPlane> opXYPlane = IGraphicsPlane::Create();
            DBG_WARN_AND_BREAK_UNLESS(opXYPlane, L"opXYPlane为空",L"GDMP",L"2023-12-30");
            opSnapContext->SetSnapPlane(TransferOwnership(opXYPlane));
        }
        // 坐标系是否用前一条模型线作为X轴，这样正交捕捉会使用前一条线作为X轴
        else if (CmdSnapModuleEntry::DoRotateCoordinate())
        {
            if (snapPoints.size() > 1)
            {
                int size = (int)snapPoints.size();
                Vector3d newXDir = snapPoints.at(size - 1) - snapPoints.at(size - 2);

                if (!Vector3dUtils::IsEqual(newXDir, Vector3d::Zero, Constants::MIN_MODEL_SIZE))
                {
                    const IGraphicsPlane& pSnapPlane = opSnapContext->GetSnapPlane();
                    Vector3d newYDir = pSnapPlane.GetNormal().Cross(newXDir);

                    OwnerPtr<IGraphicsPlane> opGraphicsPlane = IGraphicsPlane::CreateByDirections(pSnapPlane.GetOrigin(), newXDir, newYDir);
                    opSnapContext->SetSnapPlane(TransferOwnership(opGraphicsPlane));
                }
            }
        }

        // UiSnapUtils::InitSnapContextFromUiView初始化内容不包含三维正交捕捉的Coordinate，例程中将三维正交捕捉和SnapPlane设为一样
        const IGraphicsPlane& pSnapPlane = opSnapContext->GetSnapPlane();
        Coordinate3d snapCoord = Coordinate3d(pSnapPlane.GetOrigin(), pSnapPlane.GetDirectionU(), pSnapPlane.GetDirectionV());
        opSnapContext->SetCoordinate(snapCoord);

        snapDlg->SetSnapContext(opSnapContext.get());
        snapDlg->SetSnapSettings(pSnapSettings);

        int tolerance = CmdSnapModuleEntry::GetTolerance();
        pSnapSettings->SetSnapDistanceForLocalSnap(tolerance);
        pSnapSettings->SetSnapDistanceForRemoteSnap(tolerance);
        pSnapSettings->SetSnapDistanceForNonreferenceSnap(tolerance);

        opSnapContext->SetSnapSettings(pSnapSettings);

        // 返回值：拾取的点
        Vector3d selectedPoint;

        // 开始拾取
        InteractivePicker::PickerResult result = InteractivePicker::GetPoint(opSnapContext.get(), pLastPoint, L"",
            L"InteractivePicker::GetPoint", TransferOwnership(opSamplePickCallBack), selectedPoint, false, snapPlaneType, snapPointType);

        // 处理返回值
        if (result.FinishStatus == InteractivePicker::PickerFinishStatus::Successful)
        {
            if (!isFirst)
            {
                if (Vector3dUtils::IsEqual(selectedPoint, lastPoint))
                {
                    continue;
                }

                IDocument* pDocument = UiDocumentViewUtils::GetCurrentDocument();
                DBG_WARN_AND_BREAK_UNLESS(pDocument, L"pDocument为空",L"GDMP",L"2023-12-30");
                OwnerPtr<IUserTransaction> opUserTransaction = IUserTransaction::Create(pDocument, GBMP_TR(L"绘制模型线"));
                DBG_WARN_AND_BREAK_UNLESS(opUserTransaction, L"创建opUserTransaction失败",L"GDMP",L"2023-12-30");

                OwnerPtr<ILine3d> opLine3d = ILine3d::Create(lastPoint, selectedPoint);
                DBG_WARN_AND_BREAK_UNLESS(opLine3d, L"opLine3d为空",L"GDMP",L"2023-12-30");
                IModelLine* pModelLine = IModelLine::Create(pDocument, TransferOwnership(opLine3d));
                DBG_WARN_AND_BREAK_UNLESS(pModelLine, L"pModelLine为空",L"GDMP",L"2023-12-30");
                opUserTransaction->Commit();
            }
            lastPoint = selectedPoint;
            snapPoints.push_back(selectedPoint);
        }
        else
        {
            break;
        }
    }
    return nullptr;
}
#pragma endregion CmdSnapWithInteractivePicker

